כיצד מסננים קלטים ונתונים המתקבלים מידי המשתמש בעזרת PHP .
ובכן, הרבה אנשים שואלים בתמימות "כיצד אני יכול לאבטח את האתר שלי מפני התקפות כאלו ואחרות" .
ובכן ישנן התקפות מסויימות (כדוגמת XSS, SQLi) הקורות בד"כ מרשלנות יתר או חוסר תשומת לב לאי סינון קלטי קוד .
במאמר הבא נלמד כיצד להגן ולסנן קלטים המתקבלים מהמשתמשים .
סינון קלטים מספריים
ובכן כמעט בכל מערכת ישנו סינון לפי ID המתבקש מידי המשתמש דרך בקשת GET כזו או אחרת .
דוגמא חיה ממערכת הפורומים של האתר: http://phpguide.co.il/forum/index.php?action=profile;u=20
אם תשימו לב ישנה בקשת GET בשם u המבקשת את המספר 20 (מספר הID של המשתמש) .
ובכן, מה שהמערכת מצפה לקבל מהמשתמש הוא מספר שלאחר מכן בעזרתו היא שולפת את פרטי הפרופיל של המשתמש עם הID שהתקבל באותו הGET; מה קורה אם המשתמש מתחכם ומחליט לא להכניס מספר ?
במקרה שאנחנו לא מסננים את הקלטים שאנחנו מקבלים מהמשתמש אנחנו בבעיה גדולה מאוד, שכן המשתמש יכול להכניס כל דבר שיעלה על רוחו ואף לגרום נזק לנתונים בDB שלנו .
PHP מציעה לנו פונקציה נהדרת למקרה מסוג זה בשם intval .
מה שintval עושה זה לקחת את המספר הראשון המופיע בקלט המוגש לה ולהחזיר מספר שהוא אינטג'ר (INT [שלם]) .
אם אין בתו הראשון של הקלט שמוגש לה מספר היא מחזירה 0 (FALSE) .
להלן מספר דוגמאות של קלטים שהיא תקבל ומה שהיא תחזיר בתמורתה הנדיבה:
echo intval( '1' ); // 1
echo intval( '1 or 1=1' ); // 1
echo intval( '2=2' ); // 2
echo intval( '1++' ); // 1
echo intval( array() ); // 0
echo intval( TRUE ); // 1
echo intval( @date('Y/m/d') ); // 2011
echo intval( '1 or 1=1' ); // 1
echo intval( '2=2' ); // 2
echo intval( '1++' ); // 1
echo intval( array() ); // 0
echo intval( TRUE ); // 1
echo intval( @date('Y/m/d') ); // 2011
ובכן, במידה שנרצה שGET מסויים (נקרא לו נניח userId) יוכל לקבל רק מספר כקלט, כל שנעשה הוא את הצעד הבא ואנחנו מוגנים מפני קלטים בלתי רצויים:
$_GET['userId'] = intval($_GET['userId']);
במקרה כזה, נוכל אפילו להציג דף שגיאה אם התוצאה לא מקובלת עלינו:
if( $_GET['userId'] === 0 )
{
echo 'foo';
// display error ..
}
else
{
// get data from database
}
{
echo 'foo';
// display error ..
}
else
{
// get data from database
}
כמו שאמרנו intval מחזירה לנו מספר שלם ומסננת אותו, אך מה אם המשתמש הכניס מספר שלילי ?
לשם כך קיימת לנו הפונקציה abs שתפקידה הוא להחזיר ערך מוחלט של קלט מסויים (למחרוזות יוחזר ערך מוחלט אך ורק אם המספר מופיע ראשון, בדיוק כמו intval) .
כך שאם למשל המשתמש יכניס -2, לאחר מיפוי הנתונים והשתמשות בabs נקבל 2 .
בואו נראה מספר דוגמאות לשימושים בפונקציה:
echo abs( '-1' ); // 1 (type = int)
echo abs( "34.55" ); // 34.55 (type = float)
echo abs( -52.01 ); // 52.01 (type = float)
echo abs( array() ); // false
echo abs( 'hello' ); // 0 (type = int)
echo abs( '-1 or 1=1' ) // 1 (type = int)
echo abs( "34.55" ); // 34.55 (type = float)
echo abs( -52.01 ); // 52.01 (type = float)
echo abs( array() ); // false
echo abs( 'hello' ); // 0 (type = int)
echo abs( '-1 or 1=1' ) // 1 (type = int)
אם כן בואו ו"נשכלל" את הקוד שיצרנו לסינון $_GET['userId'] ונוסיף אפשרות לסינון של ערך מוחלט:
$_GET['userId'] = abs($_GET['userId']);
אם כן נטרלנו את האפשרות להכניס כל דבר שהוא לא מספר ואף מספר שלילי .
חשוב לדעת שיש לintval ארגמנט שני אופציונלי המציין את בסיס ההמרה והוא מוגדר כברירת מחדל על 10 .
סינון קלטי HTML והברחת תווים מסוכנים
בואו ניקח לנו לדוגמא אתר כמו רשת חברתית כדוגמת מייספייס .
רשתות חברתיות מסוגים כאלו נותנות אפשרות להשתמש בHTML כחלק מעריכת הפרופיל, אך כמובן שזהו צעד מאוד מאוד מסוכן .
לכן, יש לסנן ולהגדיר מראש באילו תגים נרשה להשתמש .
במקרים שאנחנו לא מוודאים אך גם לא מרשים להכניס HTML ישנן שתי אפשרויות:
האחת, לעשות REPLACE לכל התגים ולא לאפשר כל להכניס תווים הקשורים לתגים ובכללם >, <, ", ' וכד' ..
השניה, לאפשר להקיש תווים אלו אך לעשות להם המרה לHTMLENTITIES, כך שבמקרה שנקיש למשל " נקבל בקוד " אך מבחינת תצוגה נראה אותו הדבר (") .
אפשרות ראשונה
אם ברצוננו לא לאפשר כלל להקיש תווי תגים נוכל להשתמש בstr_replace ובו נגדיר מערך מוגדר מראש או לחלופין נוכל לחבר ביטוי רגולרי ואז לחסום אפשרות להכניס את התווים האלו במסגרת של תגים אך במסגרת רגילה ובודדת נאפשר .
אפשרות שנייה
PHP מציעה לנו כלים נהדרים לטיפול בתווי HTML מיוחדים .
אני אדבר על שתי פונקציות שנחוצות: htmlentities, htmlspecialchars .
הפונקציה htmlespecialchars ממירה תווים "מיוחדים" שמצויים בHTML לHTMLENTITIES .
מהם תווים מיוחדים ?
ובכן, התווים המיוחדים שהפונקציה ממירה הם &, ", ', >, < .
הפונקציה מקבלת ארבעה ארגמנטים שמתוכם רק ארגמנט אחד הוא חובה (המחרוזת שאותה רוצים להמיר) .
הארגמנט השני מקבל קבועים המיוחדים לפונקציה זו ולפונקציה htmlentities שעוד נדבר עליה בהמשך (את הקבועים ניתן לראות בקישור הבא: רשימת קבועי HTMLENTITIES וברירת המחדל מביניהם הוא ENT_COMPAT .
הארגמנט השלישי מקבל את הקידוד שנרצה לתווים המומרים לHTMLENTITIES .
הארגמנט הרביעי מקבל בוליאן (TRUE / FALSE) שמורים לפונקציה האם להמיר מחרוזת שהוגשה כמומרת או לא (לדוגמה האם להמיר את המחרוזת & ל&amp; או פשוט להשאיר אותה כמו שהיא הוגשה .
ובכן בואו נראה שימושים שונים עם פונקציה זו:
/*
נניח ש:
$_POST['userStatus'] = '<b>&היי כולם, אני מנסה לעשות טקסט מודגש, בואו נראה אם זה יצליח לי !</b>';
*/
echo htmlspecialchars( $_POST['userStatus'] ); // <b>&amp;היי כולם, אני מנסה לעשות טקסט מודגש, בואו נראה אם זה יצליח לי !</b>
echo htmlspecialchars( $_POST['userStatus'], ENT_COMPAT, 'UTF-8', FALSE ); // <b>&היי כולם, אני מנסה לעשות טקסט מודגש, בואו נראה אם זה יצליח לי !</b>
נניח ש:
$_POST['userStatus'] = '<b>&היי כולם, אני מנסה לעשות טקסט מודגש, בואו נראה אם זה יצליח לי !</b>';
*/
echo htmlspecialchars( $_POST['userStatus'] ); // <b>&amp;היי כולם, אני מנסה לעשות טקסט מודגש, בואו נראה אם זה יצליח לי !</b>
echo htmlspecialchars( $_POST['userStatus'], ENT_COMPAT, 'UTF-8', FALSE ); // <b>&היי כולם, אני מנסה לעשות טקסט מודגש, בואו נראה אם זה יצליח לי !</b>
htmlentities עושה אותו הדבר כמו htmlspecialchars ומקבל אותם ארגמנטים באותו סדר בדיוק אך ישנו הבדל קטן ביניהם, HTMLENTITIES ימיר גם תווים "מוזרים" שאינם רק מהחמישה שhtmlspecialchars ממירה .
הברחת גרשיים
לא פעם יכול המשתמש לשים למשל בתוכן שלו גם גרשיים (ציטוטים וכד') .
ניתן להמירם בעזרת HTMLENTITIES / HTMLSPECIALCHARS אך במידה ולא נרצה בכך נוכל להשתמש בפונקציה addslashes, פונקציה זו מוסיפה מתחת לגרש/יים תו המבריח אותם (\) .
ראוי להדגיש ולציין שיש להשתמש בפונקציה זו רק במידה שmagic_quotes_gpc בשרת מכובה (magic_quotes_gpc עושה לPOST, GET, COOKIES הברחת גרש/יים באופן אוטומטי ללא התערבות המשתמש) .
כמו כן בהכנסה למסד שPHP אינו תומך בו בהברחת גרש/יים יש להשתמש בפונקציה זו .
במסדים כמו MySQL, PostgreSQL וכד' שבהם יש תמיכה בPHP להברחת תווים יש להשתמש בהם (mysql_real_escape_string, pg_escape_string) .
דוגמאות שימוש:
echo addslashes('"foo'); // \"foo
echo addslashes( 'boo"foo"' ); // boo\"foo\"
echo addslashes( 'boo"foo"' ); // boo\"foo\"
הערות ודברים שחשוב לזכור
* יש תמיד לסנן קלטים .
* PHP מציעה מגוון כלים ופונקציות שבעזרתם ניתן לסנן קלטים .
* יש לבדוק ולסנן כל קלט שאינו ידוע לנו מה הסוג שלו .
במדריך הבא בעז"ה אדבר על פילטרים - כלי נהדר לסינון ולבדיקת קלטים (אימיילים, סטרינגים, IP ועוד ..)
תגובות לכתבה:
החלק הראשון של המדריך כולו עלי :-)
בשביל לסנן קלט מתגי html קיימת פונקציה strip_tags שעדיפה בהרבה יותר בזכות מהירות העבודה שלה וגם בזכות האפשרות כפרמטר שני לרשום - אילו תגים לאפשר ולא לסנן.
לדעתי זהו רעיון רע מאוד לסנן תגיות html לגמרי. לצורך המחשה בדיוק בתגובה הזו ארצה לדבר על התגית <b>. אם הטקסט הזה היה מסונן, לא הייתם רואים אותו בכלל.
במקום למחוק אותם לגמרי, עדיף פשוט להציג אותם באופן טקסטואלי (על ידי הפיכתם מתגיות לטקסט בעזרת htmlspecialchars) ולא לשנות את התוכן המקורי שהמשתמש הכניס.
כשעובדים עם קידוד utf-8 (ואני מאוד מקוהה שאתם עובדים עם קידוד utf-8) - יש לקרוא לפונקציה htmlspecialchars עם כל שלושת הפרמטרים שלה באופן הבא:
htmlSpecialChars($data, ENT_QUOTES, 'UTF-8'); //x
וככל אצבע יש לזכור שלהבטחה מפני XSS ו SQL Injection יש לבצע שני פעולות בלבד:
א. בעת הכנסת נתונים למסד לבצע הברחה עם mysql_real_escape_string
ב. בעת ההדפסה של הנתונים למשתמש (שזה יכול להיות סקריפט אחר אצל משתמש אחר ביום אחר) - להשתמש ב htmlSpecialChars עם 3 פרמטרים.
זהו :)
כמובן, ציינתי את strip_tags .
וגם ציינתי את האפשרות שבה לאפשר תגים מסויימים, שזו מעלה מצויינת .
דבר שני, הזכרתי שעדיף להשתמש בפונקציות של הדרייברים של הDB ובמקרה שאין אז להשתמש בaddslashes .
ולדעתי ברור מאליו שמתי שמדפיסים לעשות את הhtmlspecialchars .
ואני גם מקווה שכולם עובדים עם utf-8 (ומי שלא, זה הזמן !)